#include <algorithm>
#include <deque>
#include <sstream>
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>

// Maybe deque is more effective than vector in such a situation in which the words' number is unknow.
typedef std::deque<std::string> strline; // A deque which contains string, a line of string
typedef std::deque<strline> strlines; // Two-dimensional deque which contains the vector defined above


// Get the gravity value of particular word
double gravity(const std::string& word) 
{
	double ascii = 0;
	for (auto i = word.begin(); i != word.end(); ++i)
	{
		ascii += *i;
	}
	return ascii / word.size();
}


// 1. A functor, with which the std::sort() can compare two different words with its gravity value. 
// 2. This is used in "Data::all_max_gravity_words()" to sort words in each line,
// therefore in each line, words with max gravity value can be sorted to the front side.
// By doing this they can be easily picked out.
class cmp_with_gravity 
{
public:
	bool operator()(const std::string& word1, const std::string& word2)
	{
		return gravity(word1) > gravity(word2);
	}
};

// declaration of Data class, but get_lines() is defined inside the class
class Data
{
private:
	strline particular_line_;
	strlines all_lines_;
	std::string directory_;
	auto get_lines() -> decltype(all_lines_) const { return all_lines_; }
	strlines all_max_gravity_words() const;
public:

	Data(const std::string& directory);

	// read lines
	void read_file_by_line();

	// print values on console
	void formatted_print() const;

	// output wordValue.txt file
	void write_down(const std::string& fileName) const;
};


inline Data::Data(const std::string& directory) : directory_(directory) {}

// read lines
inline void Data::read_file_by_line() 
{
	std::ifstream infile(directory_);
	std::stringstream ss;
	ss << infile.rdbuf(); // make the text file a stringstream
	
	std::string line;
	std::stringstream sstemp(line);
	std::string strtemp;
	while (std::getline(ss, line))
	{
		std::stringstream sstemp(line);

		while (sstemp >> strtemp)
		{
			particular_line_.push_back(strtemp); // current reading line
		}
		if (!particular_line_.empty()) // if last read line is not a blank one.  ( if this statement not added , error occurs)
			all_lines_.push_back(particular_line_);
		particular_line_.clear();
	}
	infile.close();
} 

// all words with max gravity in each line
inline strlines Data::all_max_gravity_words() const  
{
	if (all_lines_.empty())
	{
		throw std::bad_exception();
	}


	// In order to prevent modifying raw data, had a deep copy on it.
	// deep_copy is a variable which contains the same thing as all_lines_ .
	strlines deep_copy = all_lines_; 


	// Have a sort on words in each line.
	// Therefore words with max gravity value can be arranged to front.
	for (auto iter = deep_copy.begin(); iter != deep_copy.end(); ++iter)
	{
		std::sort(iter->begin(), iter->end(), cmp_with_gravity()); // 
	}

	strlines ret; // return value
	double current_line_max_ascii = 0;
	strline max_gravity_words; // a deque that contains max-gravity-value strings in particular line

	for (auto iter = deep_copy.begin(); iter != deep_copy.end(); ++iter)
	{
		current_line_max_ascii = gravity(*(iter->begin()));

		for (auto iter2 = iter->begin(); iter2 != iter->end(); iter2++) // find all words with max gravity but they are different
		{
			if (gravity(*iter2) == current_line_max_ascii && 
				std::find(max_gravity_words.begin(), max_gravity_words.end(), *iter2) == max_gravity_words.end())
				// the long condition above avoids adding same words
			{
				max_gravity_words.push_back(*iter2);
			}
			else
			{
				break;
			}
		}
		ret.push_back(max_gravity_words);
		max_gravity_words.clear();
	}
	return ret;
}

// print values on console
inline void Data::formatted_print() const 
{
	auto words_with_max_gravity = all_max_gravity_words();
	for (auto iter = words_with_max_gravity.begin(); iter != words_with_max_gravity.end(); ++iter)
	{
		for (auto iter2 = iter->begin(); iter2 != iter->end(); ++iter2)
		{
			std::cout << *iter2 << " ";
		}
		std::cout << std::fixed << std::setprecision(3) << gravity(*(iter->begin())) << std::endl;
	}
}

// output wordValue.txt file
void Data::write_down(const std::string& file) const 
{
	std::ofstream outfile(file);
	auto words_with_max_gravity = all_max_gravity_words();
	for (auto iter = words_with_max_gravity.begin(); iter != words_with_max_gravity.end(); ++iter)
	{
		for (auto iter2 = iter->begin(); iter2 != iter->end(); ++iter2)
		{
			outfile << *iter2 << " ";
		}
		outfile << std::fixed << std::setprecision(3) << gravity(*(iter->begin())) << std::endl;
	}
}
int main()
{
	Data data = Data("C:\\data\\data.txt");
	data.read_file_by_line();
	data.formatted_print();
	data.write_down("C:\\data\\wordValue.txt");
	system("pause");
}

